package com.springframework.contexttwo;

import com.springframework.annotation.*;

import java.io.File;
import java.io.FileFilter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;


public class ShAnnotationConfigApplicationContext implements ShApplicationContext {
    // 保存所有的bean实例对象
    private Map<String, Object> beanMap = new HashMap<>();

    // 保存所有的bean定义信息
    public Map<String, ShBeanDefinition> beanDefinitionMap = new HashMap<>();

    public ShAnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
        for (Class<?> cls : annotatedClasses) {
            parseBeanAnnotation(cls);

            parseComponentScan(cls);

            try {
                /**
                 * 创建 bean实例对象，并放入beanMap中
                 */
                creatBean();

                /**
                 * 完成依赖注入
                 */
                doDI();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void doDI() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
            String beanId = entry.getKey();
            Object beanObj = entry.getValue();
            Field[] fields = beanObj.getClass().getDeclaredFields();
            for (Field field : fields) {
                if( field.isAnnotationPresent(YcResource.class) ){
                    YcResource ycResource = field.getAnnotation(YcResource.class);
                    String resourceId = ycResource.value();

                    Object obj = getToDiObject(resourceId);

                    // 设置此 filed 为可访问共有的
                    field.setAccessible(true);
                    field.set(beanObj, obj);
                }else if (field.isAnnotationPresent(YcAutowired.class)) {
                    // todo: 取出属性的类型，按照类型先从 beanMap中返回，如果没有再从beanDefinitionMap中操作
                    Class<?> fieldType = field.getType();
                }
            }
        }

    }

    private Object getToDiObject(String resourceId) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        if (resourceId.equals("")) {
            return null;
        }
        if ( beanMap.containsKey(resourceId) ){
            return beanMap.get(resourceId);
        }
        if ( !beanDefinitionMap.containsKey(resourceId)){
            throw new RuntimeException("定义Map中， 没有找到beanID为：" + resourceId + "的bean");
        }
        ShBeanDefinition beanInfo = beanDefinitionMap.get(resourceId);
        if ( beanInfo.Lazy()  ){
            String classInfo = beanInfo.getClassInfo();
            Object beanObj = Class.forName(classInfo).newInstance();
            beanMap.put(resourceId, beanObj);
            return beanObj;
        }


        if ("prototype".equals(beanInfo.getScope())){
            String classInfo = beanInfo.getClassInfo();
            Object beanObj = Class.forName(classInfo).newInstance();   // 创建对象
            return beanObj;  // 不是懒加载，直接返回对象
        }

        return null;
    }

    private void creatBean() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        for (Map.Entry<String, ShBeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanId = entry.getKey();
            ShBeanDefinition beanInfo = entry.getValue();
            if ( beanInfo.Lazy() ==false || "prototype".equals(beanInfo.getScope())) {
                String classInfo = beanInfo.getClassInfo();
                Object obj = Class.forName(classInfo).newInstance();
                beanMap.put(beanId, obj);
            }
        }
    }

    private void parseComponentScan(Class<?> cls) {
        if (cls.isAnnotationPresent(YcComponentScan.class)) {
            YcComponentScan ycComponentScan = cls.getAnnotation(YcComponentScan.class);
            String[] basePackages = ycComponentScan.value();
            if (basePackages == null || basePackages.length <= 0) {
                basePackages = ycComponentScan.basePackages();
            }
            if (basePackages == null || basePackages.length <= 0) {
                basePackages = new String[1];
                basePackages[0] = cls.getPackage().getName();
            }
            for (String basePackage : basePackages) {
                System.out.println("要扫描的路径：  " + basePackage);
            }

            recursiveLoadBeanDefinition(basePackages);
        }
    }

    private void recursiveLoadBeanDefinition(String[] basePackages) {
        for (String onePackageName : basePackages) {
            try {
                String packagePath = onePackageName.replaceAll("\\.", "/");
                Enumeration<URL> files = Thread.currentThread().getContextClassLoader().getResources(packagePath);
                if (files.hasMoreElements()) {
                    URL url = files.nextElement();
                    findPackageClasses(url.getFile(), onePackageName);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void findPackageClasses(String filePath, String onePackageName) {
        if (filePath.startsWith("/")) {
            filePath = filePath.substring(1);
        }
        try {
            filePath = URLDecoder.decode(filePath, "utf-8"); // 防止路径中文，统一转utf-8
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        File file = new File(filePath);   // 所有文件和文件夹   如果文件夹是空的，这里是没有的
        File[] classFiles = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                // 如果扫描到的是文件夹或者是.class文件，则返回true
                if (pathname.getName().endsWith(".class") || pathname.isDirectory()) {
                    return true;
                } else {
                    return false;
                }
            }
        });
//        for (File classFile : classFiles) {
//            System.out.println(classFile.getName());
//        }
//        System.out.println();
        if (classFiles != null || classFiles.length > 0) {
            for (File classFile : classFiles) {
                if (classFile.isDirectory()) {
                    findPackageClasses(classFile.getAbsolutePath(), onePackageName + "." + classFile.getName());
                } else {
                    try {
                        URLClassLoader uc = new URLClassLoader(new URL[]{});
                        Class cls = uc.loadClass(onePackageName + "." + classFile.getName().replace(".class", ""));
                        if (
                                cls.isAnnotationPresent(YcComponentScan.class) ||
                                        cls.isAnnotationPresent(YcComponent.class) ||
                                        cls.isAnnotationPresent(YcService.class) ||
                                        cls.isAnnotationPresent(YcController.class) ||
                                        cls.isAnnotationPresent(YcRepository.class)
                        ) {
                            ShBeanDefinition shBeanDefinition = new ShBeanDefinition();
                            if (cls.isAnnotationPresent(YcLazy.class)) {
                                YcLazy ycLazy = (YcLazy) cls.getAnnotation(YcLazy.class);
                                boolean lazy = ycLazy.value();
                                shBeanDefinition.setLazy(lazy);
                            }
                            if (cls.isAnnotationPresent(YcScope.class)) {
                                YcScope ycScope = (YcScope) cls.getAnnotation(YcScope.class);
                                String scope = ycScope.value();
                                shBeanDefinition.setScope(scope);
                            }
                            String classInfo = onePackageName + "." + classFile.getName().replace(".class", "");
                            shBeanDefinition.setClassInfo(classInfo);
                            String beanId = getBeanId(cls);
                            beanDefinitionMap.put(beanId, shBeanDefinition);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }

    private String getBeanId(Class cls) {
        String beanId = "";
        YcComponent ycComponent = (YcComponent) cls.getAnnotation(YcComponent.class);
        YcService ycService = (YcService) cls.getAnnotation(YcService.class);
        YcController ycController = (YcController) cls.getAnnotation(YcController.class);
        YcRepository ycRepository = (YcRepository) cls.getAnnotation(YcRepository.class);
        if (ycComponent != null) {
            beanId = ycComponent.value();
        }else  if (ycRepository != null) {
            beanId = ycRepository.value();
        } else if (ycService != null) {
            beanId = ycService.value();
        } else if (ycController != null) {
            beanId = ycController.value();
        }
        if ("".equals(beanId)){
            String simpleName = cls.getSimpleName();
            beanId = simpleName.substring(0,1).toLowerCase() + simpleName.substring(1);
        }
        return beanId;
    }

    private void parseBeanAnnotation(Class<?> cls) {
        try {
            Object obj = cls.newInstance();
            String simpleName = cls.getSimpleName();   // 获取类名
            ShBeanDefinition bd = new ShBeanDefinition();

            bd.setLazy(false);
            bd.setScope("singleton");
            bd.setClassInfo(cls.getName());
            String beanId = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
            beanDefinitionMap.put(beanId, bd);
            beanMap.put(beanId, obj);

            Method[] declaredMethods = cls.getDeclaredMethods();
            ;
            for (Method method : declaredMethods) {
                if (method.isAnnotationPresent(YcBean.class)) {
                    YcBean ycBean = method.getAnnotation(YcBean.class);
                    String ycBeanId = ycBean.value();
                    if ("".equals(ycBeanId)) {
                        ycBeanId = method.getName();
                    }
                    ycBeanId = ycBeanId.substring(0, 1).toLowerCase() + ycBeanId.substring(1);
                    ShBeanDefinition shBeanDefinition = new ShBeanDefinition();
                    shBeanDefinition.setLazy(false);
                    shBeanDefinition.setScope("singleton");
                    shBeanDefinition.setClassInfo(method.getReturnType().getName());
                    beanDefinitionMap.put(ycBeanId, shBeanDefinition);
                    System.out.println();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object getBean(String beanID) {
        ShBeanDefinition bd = this.beanDefinitionMap.get(beanID);
        if (bd == null){
            throw new RuntimeException("beanId:"+beanID+"不存在");
        }
        String scope = bd.getScope();
        if ("prototype".equals(scope)){
            Object obj = null;
            try {
                obj = Class.forName(bd.getClassInfo()).newInstance();
                this.beanMap.put(beanID,obj);
                doDI();
                this.beanMap.remove(beanID);
                return obj;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }
        }
        if (beanMap.containsKey(beanID)){
            return beanMap.get(beanID);
        }
        if (bd.Lazy()){
            Object obj = null;
            try {
                obj = Class.forName(bd.getClassInfo()).newInstance();
                beanMap.put(beanID,obj);
                doDI();
                return obj;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }
}
